package visitor;

import symbol.*;
import symbol.Class;
import syntaxtree.*;

import java.util.HashMap;

import error.Error;

public class TypeCheckVisitor implements Visitor {
	Class currClass;
	Method currMethod;
	Class currClassExtend; // usado para percorrer quando classe extend outra
	HashMap<String, Class> symbolTable;
	String type; // Uso pra saber o tipo de retorno entre as chamadas...
	public Error error = Error.getInstance();


	public void setSymbolTable(HashMap<String, Class> c) {
		this.symbolTable = c;    
	}

	public void visit(Program n) {
		n.m.accept(this);
		for(int i = 0; i < n.cl.size(); i++) n.cl.elementAt(i).accept(this);            
	}

	public void visit(MainClass n) {
		n.s.accept(this);
	}

	public void visit(ClassDeclSimple n) {
		currClass = symbolTable.get(n.i.s); // pego a tabela da classe
		for(int i = 0; i < n.ml.size() ; i++) n.ml.elementAt(i).accept(this);
		currClass = null; // fim do escopo da classe
	}

	public void visit(ClassDeclExtends n) {
		currClass = symbolTable.get(n.i.s);
		for(int i = 0; i < n.ml.size() ; i++) n.ml.elementAt(i).accept(this);
		currClass = null;
	}

	public void visit(VarDecl n) {}

	public void visit(MethodDecl n) {
		for(int i = 0; i < n.sl.size() ; i++) 
			n.sl.elementAt(i).accept(this);
		currMethod = null;
	}

	public void visit(Formal n) {}
	public void visit(IntArrayType n) {}
	public void visit(BooleanType n) {}
	public void visit(IntegerType n) {}
	
	public void visit(IdentifierType n) {
		if(!this.symbolTable.containsKey(n.s)) 
			error.addErro("IdentifierType: This type is not defined: "+n.s+ "\n"); 
	}

	public void visit(Block n) {
		for(int i = 0; i < n.sl.size() ; i++) 
			n.sl.elementAt(i).accept(this);
	}

	public void visit(If n) {
		n.e.accept(this);
		if(!type.equals("bool")) 
			error.addErro("If: Evaluated expression is not a bool \n");  
		n.s1.accept(this);
		n.s2.accept(this);
	}

	public void visit(While n) {
		n.e.accept(this);
		if(!type.equals("bool")) 
			error.addErro("While: Evaluated expression is not a bool \n");  
		n.s.accept(this);
	}

	public void visit(Print n) {
		n.e.accept(this);
	}

	public void visit(Assign n) {
		String typeAux;
		n.i.accept(this);
		typeAux = type;
		n.e.accept(this);
		if(!(typeAux.equals(type)))
			error.addErro("Assign: Different types, Id: "+typeAux+" Exp: "+type+"\n");
	}

	public void visit(ArrayAssign n) {
		n.i.accept(this);
		if(!(type.equals("intarray")))
			error.addErro("ArrayAssign: The array type must be int.\n");

		n.e1.accept(this);
		if(!(type.equals("int")))
			error.addErro("ArrayAssign: The array index must be integer.\n");

		n.e2.accept(this);
		if(!(type.equals("int")))
			error.addErro("The expression must be integer.\n");
	}

	public void visit(And n) {
		n.e1.accept(this);
		if(!(type.equals("bool")))
			error.addErro("AndExpression: The operand must be boolean.\n");

		n.e2.accept(this);
		if(!(type.equals("bool")))
			error.addErro("AndExpression: The operand must be boolean.\n");
		type = "bool";
	}

	public void visit(LessThan n) {
		n.e1.accept(this);
		if(!(type.equals("int")))
			error.addErro("LessThanExpression: The operand must be integer.\n");

		n.e2.accept(this);
		if(!(type.equals("int")))
			error.addErro("LessThanExpression: The operand must be integer.\n");
		type = "bool";
	}

	public void visit(Plus n) {
		n.e1.accept(this);
		if(!(type.equals("int")))
			error.addErro("PlusExpression: The operand must be integer.\n");

		n.e2.accept(this);
		if(!(type.equals("int")))
			error.addErro("PlusExpression: The operand must be integer.\n");
		type = "int";
	}

	public void visit(Minus n) {
		n.e1.accept(this);
		if(!(type.equals("int")))
			error.addErro("MinusExpression: The operand must be integer.\n");

		n.e2.accept(this);
		if(!(type.equals("int")))
			error.addErro("MinusExpression: The operand must be integer.\n");
		type = "int";
	}

	public void visit(Times n) {
		n.e1.accept(this);
		if(!(type.equals("int")))
			error.addErro("TimesExpression: The operand must be integer.\n");

		n.e2.accept(this);
		if(!(type.equals("int")))
			error.addErro("TimesExpression: The operand must be integer.\n");
		type = "int";
	}

	public void visit(ArrayLookup n) {
		n.e1.accept(this);
		if(!(type.equals("intarray")))
			error.addErro("ArrayLookup: The array type must be int.\n");

		n.e2.accept(this);
		if(!(type.equals("int")))
			error.addErro("ArrayLookup: The array index must be integer.\n");
		type = "int";
	}

	public void visit(ArrayLength n) {
		n.e.accept(this);
		if(!(type.equals("intarray")))
			error.addErro("ArrayLength: The array type must be int.\n");
		type = "int";
	}

	public void visit(Call n) {
		Class classAux;
		Class classExtended;
		Class classExtendedParam;
		Method method;

		n.e.accept(this);
		if(symbolTable.containsKey(type)){
			classAux = symbolTable.get(type);
			if(classAux.containsMethod(n.i.s)){
				method = classAux.getMethod(n.i.s);
				for(int i=0;i<method.getParameterSize();i++){
					n.el.elementAt(i).accept(this);

					if(symbolTable.containsKey(method.getParameterType(i))){
						classExtendedParam = symbolTable.get(type);
						while(classExtendedParam.getExtender()!=null){
							if(type.equals(method.getParameterType(i))) break;
							else{
								classExtendedParam = symbolTable.get(classExtendedParam.getExtender());
								type = classExtendedParam.getClassName();
							}
						}
					}

					if(!(type.equals(method.getParameterType(i))))
						error.addErro("Call: The type of the parameter " + i +" must be " + method.getParameterType(i) +".\n");
				}
				type = method.getReturnType().toString();
			}
			else if(classAux.getExtender()!=null){
				while(this.currClassExtend != null){
					classExtended = symbolTable.get(classAux.getExtender());
					if(classExtended.containsMethod(n.i.s)) {
						method = classAux.getMethod(n.i.s);
						for(int i=0;i<method.getParameterSize();i++){
							n.el.elementAt(i).accept(this);
							if(symbolTable.containsKey(method.getParameterType(i))){

								classExtendedParam = symbolTable.get(type);
								while(classExtendedParam.getExtender()!=null){
									if(type.equals(method.getParameterType(i))) break;
									else{
										classExtendedParam = symbolTable.get(classExtendedParam.getExtender());
										type = classExtendedParam.getClassName();
										System.out.print(type);
									}
								}
							}

							if(!(type.equals(method.getParameterType(i))))
								error.addErro("Call: The type of the parameter " + i +" must be " + method.getParameterType(i) +".\n");

						}
						type = method.getReturnType().toString();
						break;
					}
				}
			}
			else error.addErro("Call: This method is not defined: "+n.i.s+ "\n");
		}
		else error.addErro("Call: This type is not defined \n");
	}

	public void visit(IntegerLiteral n) {
		type = "int";
	}

	public void visit(True n) {
		type = "bool";
	}

	public void visit(False n) {
		type = "bool";
	}

	public void visit(IdentifierExp n) {
		Class classExtended;
		if(this.currMethod.containsParameter(n.s)){
			type = currMethod.getParameterType(n.s).toString();
		}
		else if(this.currMethod.containsVar(n.s)){
			type = currMethod.getVarType(n.s).toString();
		}
		else if(this.currClass.containsVar(n.s)){
			type = currClass.getFieldType(n.s).toString();
		}
		else if(currClass.getExtender()!=null){
			classExtended = symbolTable.get(currClass.getExtender());
			while(classExtended != null){
				if(classExtended.containsVar(n.s)) {
					type = classExtended.getFieldType(n.s).toString();
					break;
				}
				classExtended = symbolTable.get(classExtended.getExtender());
			}
		}
		else error.addErro("This variable is not defined: "+n.s+ "\n");
	}

	public void visit(This n) {
		type = currClass.getClassName();
	}

	public void visit(NewArray n) {
		n.e.accept(this);
		if(!(type.equals("int")))
			error.addErro("NewArray: The array index must be integer.\n");
		type = "intarray";
	}

	public void visit(NewObject n) {
		if(!this.symbolTable.containsKey(n.i.s)) 
			error.addErro("NewObject: This class is not defined: "+n.i.s+ "\n"); 
		type = n.i.s;
	}

	public void visit(Not n) {
		n.e.accept(this);
		if(!(type.equals("bool")))
			error.addErro("NotExpression: The operand must be boolean.\n");
		type = "bool";
	}

	public void visit(Identifier n) {
		Class classExtended;
		if(this.currMethod.containsParameter(n.s)){
			type = currMethod.getParameterType(n.s).toString();
		}
		else if(this.currMethod.containsVar(n.s)){
			type = currMethod.getVarType(n.s).toString();
		}
		else if(this.currClass.containsVar(n.s)){
			type = currClass.getFieldType(n.s).toString();
		}
		else if(currClass.getExtender()!=null){
			classExtended = symbolTable.get(currClass.getExtender());
			while(classExtended != null){
				if(classExtended.containsVar(n.s)) {
					type = classExtended.getFieldType(n.s).toString();
					break;
				}
				classExtended = symbolTable.get(classExtended.getExtender());
			}
		}
		else error.addErro("This variable is not defined: "+n.s+ "\n");
	}

	public void visit(NullType n) {
		type = "null";
	}
	
	public void visit(StringArrayType n) {
		type = "stringarray";
	}
}
